<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        /*
            1.vue将data初始化一个Observer并对对象中 的每个值重写了其中的get，和set，data中的每个key，都有一个独立的依赖收集器
            2.在get中，想依赖收集器添加监听
            3.在mount时，实例了一个Watcher，将收集器的目标指向当前Watcher
            4.在data值发生变更时，触发set，触发了依赖收集器中 的所有监听的更新，来触发Watcher.update

        */
        const Observer = function (data) {
            // 循环修改为每个属性添加get set
            for (let key in data) {
                defineReactive(data, key);
            }
        }

        const defineReactive = function (obj, key) {
            // 局部变量dep，用于get set内部调用
            const dep = new Dep();
            // 获取当前值
            let val = obj[key];
            Object.defineProperty(obj, key, {
                // 设置当前描述属性为可被循环
                enumerable: true,
                // 设置当前描述属性可被修改
                configurable: true,
                get() {
                    console.log('in get');
                    // 调用依赖收集器中的addSub，用于收集当前属性与Watcher中的依赖关系
                    dep.depend();
                    return val;
                },
                set(newVal) {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                    // 当值发生变更时，通知依赖收集器，更新每个需要更新的Watcher，
                    // 这里每个需要更新通过什么断定？dep.subs
                    dep.notify();
                }
            });
        }

        const observe = function (data) {
            return new Observer(data);
        }

        const Vue = function (options) {
            const self = this;
            // 将data赋值给this._data，源码这部分用的Proxy所以我们用最简单的方式临时实现
            if (options && typeof options.data === 'function') {
                this._data = options.data.apply(this);
            }
            // 挂载函数
            this.mount = function () {
                new Watcher(self, self.render);
            }
            // 渲染函数
            this.render = function () {
                with (self) {
                    _data.text;
                }
            }
            // 监听this._data
            observe(this._data);
        }

        const Watcher = function (vm, fn) {
            console.log(vm)//vue的实例对象
            console.log(fn)
            const self = this;
            this.vm = vm;
            // 将当前Dep.target指向自己
            Dep.target = this;
            // 向Dep方法添加当前Wathcer
            this.addDep = function (dep) {
                console.log(dep)
                dep.addSub(self);
            }
            // 更新方法，用于触发vm._render
            this.update = function () {
                console.log('in watcher update');
                fn();
            }
            // 这里会首次调用vm._render，从而触发text的get
            // 从而将当前的Wathcer与Dep关联起来
            this.value = fn();
            // 这里清空了Dep.target，为了防止notify触发时，不停的绑定Watcher与Dep，
            // 造成代码死循环
            Dep.target = null;
            console.log(this)
        }

        const Dep = function () {
            const self = this;
            // 收集目标
            this.target = null;
            // 存储收集器中需要通知的Watcher
            this.subs = [];
            // 当有目标时，绑定Dep与Wathcer的关系
            this.depend = function () {
                console.log(Dep.target)
                if (Dep.target) {
                    // 这里其实可以直接写self.addSub(Dep.target)，
                    // 没有这么写因为想还原源码的过程。
                    Dep.target.addDep(self);
                }
            }
            // 为当前收集器添加Watcher
            this.addSub = function (watcher) {
                self.subs.push(watcher);
                console.log(self.subs)
            }
            // 通知收集器中所的所有Wathcer，调用其update方法
            this.notify = function () {
                for (let i = 0; i < self.subs.length; i += 1) {
                    self.subs[i].update();
                }
            }
        }

        const vueBox = new Vue({
            data() {
                return {
                    text: 'hello world',
                };
            }
        })

        vueBox.mount(); // in get
        vueBox._data.text = '123'; // in watcher update /n in get
    </script>
<!--    <script>-->
<!--        let oldArrayPrototype=Array.prototype-->
<!--        let proto=Object.create(oldArrayPrototype)//继承-->
<!--        console.log(proto)-->
<!--        let arr=['push','shift','unshift']-->
<!--        arr.forEach(method=>{-->
<!--            proto[method]=function(){//函数劫持，把函数进行重写，内部继续调用老的方法-->
<!--                updateView()-->
<!--                oldArrayPrototype[method].call(this,...arguments)-->
<!--            }-->
<!--        })-->
<!--        function updateView(){-->
<!--            console.log('更新视图')-->
<!--        }-->
<!--        function observer(target){-->
<!--            if(typeof target !== 'object' || target === null) return-->
<!--            if(Array.isArray(target)){-->
<!--                Object.setPrototypeOf(target,proto)-->
<!--                // target.__proto__=proto-->
<!--            }-->
<!--            for(let key in target){-->
<!--                defineReactive(target,key,target[key])//重新定义数据-->
<!--            }-->
<!--        }-->
<!--        function defineReactive(target,key,value){-->
<!--            observer(value)//递归，当前key对应的value还是一个对象-->
<!--            Object.defineProperty(target,key,{-->
<!--                get(){-->
<!--                    //get中会进行依赖收集-->
<!--                    return value-->
<!--                },-->
<!--                set(newValue){-->
<!--                    if(newValue !== value){-->
<!--                        observer(newValue)//递归，解决修改的值是一个新的对象-->
<!--                        updateView()-->
<!--                        value=newValue-->
<!--                    }-->
<!--                }-->
<!--            })-->
<!--        }-->
<!--        //使用Obejct.defineProperty重新定义属性，给属性增加getter，和setter-->
<!--        // let data={name:'zf',age:{a:1}}-->
<!--        // observer(data)//观察这个数据，如果数据发生变化，就触发视图去更新-->
<!--        // data.name='jw'-->
<!--        // data.age.a=2-->

<!--        //给age重新定义了，会触发视图更新，但是修改里面的值，无法触发，因为此时的age对象没有被监听-->
<!--        // data.age={a:4}-->
<!--        // data.age.a=6-->

<!--        //递归，如果对象的层级比较大，就会无限递归，导致内存溢出-->

<!--        //问题1：如果属性不存在，新增的属性会是响应式的吗？？？-->
<!--        let data={age:[1,2,3]}-->
<!--        observer(data)-->
<!--        data.age.push(4)//不会触发视图更新，需要对数组上的方法进行重写，push，shift，unshift，pop，reverse-->

<!--    </script>-->
</body>

</html>
